import {isString, isEmpty, isFunction} from 'lodash';
import * as invariant from 'invariant';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Reducer, Store } from 'redux';
import hoistNonReactStatics from './hoistNonReactStatics';
import createReducer from './reducers';

export interface IInjectors {
    injectReducer: any;
}

export const injectReducerFactory = (store: Store<any>) => {
    return (key: string, reducer: Reducer<any>) => {
        invariant(
            isString(key) && !isEmpty(key) && isFunction(reducer),
            '(app/utils...) injectReducer: Expected `reducer` to be a reducer function'
        );

        // tslint:disable-next-line:max-line-length
        // Check `store.injectedReducers[key] === reducer` for hot reloading when a key is the same but a reducer is different
        if (Reflect.has(store.injectedReducers, key) && store.injectedReducers[key] === reducer) return;

        store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign
        store.replaceReducer(createReducer(store.injectedReducers));
    };
};

export const getInjectors = (store: Store<any>): IInjectors => {
    return {
        injectReducer: injectReducerFactory(store)
    };
};

/**
 * Dynamically injects a reducer
 *
 * @param {string} key A key of the reducer
 * @param {function} reducer A reducer that will be injected
 *
 */
export const withReducer =
    ({ key, reducer }: { key: string, reducer: Reducer<any> }) => (WrappedComponent: React.ComponentClass) => {
        class ReducerInjector extends React.Component {
            static WrappedComponent = WrappedComponent;
            static contextTypes = {
                store: PropTypes.object.isRequired
            };

            // tslint:disable-next-line:max-line-length
            static displayName = `withReducer(${(WrappedComponent.displayName || WrappedComponent.name || 'Component')})`;

            injectors = getInjectors(this.context.store);

            componentWillMount() {
                const { injectReducer } = this.injectors;

                injectReducer(key, reducer);
            }

            render() {
                return <WrappedComponent {...this.props} />;
            }
        }

        return hoistNonReactStatics(ReducerInjector, WrappedComponent, null);
    };
